/**
 * Copyright 2017 IBM All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *http://www.apache.org/licenses/LICENSE-2.0
 *
 *	Unless required by applicable law or agreed to in writing, software
 *	distributed under the License is distributed on an 'AS IS' BASIS,
 *	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *	See the License for the specific language governing permissions and
 *	limitations under the License.
 */
'use strict';
var log4js = require('log4js');
var util = require('util');
var logger = log4js.getLogger('explorer');
// var cors = require('cors');
var config = require('./config.json');
var helper = require('./helper.js');
var query = require('./query.js');
var grpc = require('grpc')
var path = require('path')
var ByteBuffer = require('ByteBuffer')
var protos = grpc.load(path.join(__dirname, '../node_modules/fabric-client/lib/protos/peer/proposal.proto')).protos
var hello = function() {}
var readStringFromBB = function(bb) {
	return bb.buffer.toString("utf8", bb.offset, bb.limit);
}
var readHexFromBB = function(bb) {
	return bb.buffer.toString("hex", bb.offset, bb.limit);
}
function unpack(str) {
    var bytes = [];
    for(var i = 0; i < str.length; i++) {
        var char = str.charCodeAt(i);
        bytes.push(char >>> 8);
        bytes.push(char & 0xFF);
    }
    return bytes;
}

//Query for Channel Information
var getChannelInfo = function() {
	logger.debug('================ GET CHANNEL INFORMATION ======================');
	logger.debug('channelName : ' + config.channelName);
	logger.debug('User name : ' + config.userName)
	logger.debug('Org name	: ' + config.orgName);
	logger.debug('Peer Name: ' + config.peerName);
	return query.getChainInfo(
		config.peerName, 
		config.userName, 
		config.orgName)
		.then( 
			ci => {
			return Promise.resolve({
				height: ci.height,
				currentBlockHash: readHexFromBB(ci.currentBlockHash),
				previousBlockHash: readHexFromBB(ci.previousBlockHash)
				}
			);
		});
}

//	Query Get Block by BlockNumber
var getBlockByNum = function(blockId) {
	logger.debug('==================== GET BLOCK BY NUMBER ==================');
	logger.debug('BlockID : ' + blockId);
	if (!blockId) {
		return Promise.reject(new Error("block id is not provided") );
	}
	return query.getBlockByNumber(
		config.peerName, 
		blockId, 
		config.userName, 
		config.orgName)
		.then( bi => {
			// logger.debug(util.inspect(bi, false, null));
			logger.debug("***************************");
			logger.info(util.inspect(bi, false, null));
			let transactions = []
			let td = bi.data.data;
			for(let i = 0; i < td.length; ++i) {
				var proposal = protos.Proposal.decode(td[i].payload.data.actions[0].payload.chaincode_proposal_payload);
				var ccpp = protos.ChaincodeProposalPayload.decode(proposal.header);
				logger.debug(util.inspect(proposal));
				var ccspec = protos.ChaincodeSpec.decode(ccpp.input);
				var args = []
				for(let i = 0; i < ccspec.input.args.length ; ++i) {
		            let bb = ccspec.input.args[i];
		            // !!! not easy to figure out
				    args.push(bb.buffer.toString("utf8", bb.offset, bb.limit));
				}
				transactions.push( {
					signature: td[i].signature,
					channel_id: td[i].payload.header.channel_header.channel_id,
					timestamp: td[i].payload.header.channel_header.timestamp,
					tx_id: td[i].payload.header.channel_header.tx_id,
					chaincodeId: protos.ChaincodeHeaderExtension.decode(new Buffer(td[i].payload.header.channel_header.extension, 'hex')).chaincode_id.name,
					args: args
				});
			}
			return Promise.resolve( {
				blockNum: bi.header.number,
				stateHash: bi.header.data_hash,
				previousBlockHash: bi.header.previous_hash,
				transactions: transactions
			})
		});
}

// Query Get Transaction by Transaction ID
var getTxByTxId = function(trxnId) {
	logger.debug('================ GET TRANSACTION BY TRANSACTION_ID ======================');
	logger.debug('tx_id ', trxnId);
	if (!trxnId) {
		return Promise.reject(new Error("trxnId is not provided") );
	}
	return query.getTransactionByID(config.peerName, trxnId, config.userName, config.orgName);
}

/*
// Query Get Block by Hash
app.get('/channels/:channelName/blocks', function(req, res) {
	logger.debug('================ GET BLOCK BY HASH ======================');
	//logger.debug('peers : '+req.body.peers);// target peers list
	logger.debug('channelName : ' + req.params.channelName);
	let hash = req.query.hash;
	let peer = req.query.peer;
	if (!hash) {
		res.json(getErrorMessage('\'hash\''));
		return;
	}
	if (!peer) {
		res.json(getErrorMessage('\'peer\''));
		return;
	}
	var token = req.body.token || req.query.token || req.headers[
		'x-access-token'];
	jwt.verify(token, app.get('secret'), function(err, decoded) {
		if (err) {
			res.send({
				success: false,
				message: 'Failed to authenticate token.'
			});
		} else {
			logger.debug('User name : ' + decoded.username);
			logger.debug('Org name	: ' + decoded.orgName);
			query.getBlockByHash(peer, hash, decoded.username, decoded.orgName).then(
				function(message) {
					res.send(message);
				});
		}
	});
});
*/


/*

// How to deal with hex string encoded ChaincodeHeaderExtension

var ext = new protos.ChaincodeHeaderExtension();
var chaincodeID = new protos.ChaincodeID();
chaincodeID.setName('hejia');
ext.setChaincodeId(chaincodeID);
logger.info("*******cid*********8");
logger.info(protos.ChaincodeHeaderExtension.encode(ext).buffer.toString("hex"));

let buf = new Buffer("1207120568656a6961", "hex");
let ext2 = protos.ChaincodeHeaderExtension.decode(buf);
logger.info(ext2);
*/
getChannelInfo()
	.then(ci => {
			logger.info("*****************");
			logger.info(ci);
			return Promise.resolve(ci);
		},
		error => console.log(error))
	.then(ci => {
		return getBlockByNum(ci.height - 1);
	})
	.then(bi=>{
		logger.info("***************Blcok***************8")
		logger.info(util.inspect(bi, false, null));
		return getTxByTxId(bi.transactions[0].tx_id);
	})
	.then(txData => {
		// logger.info('================tx data==============');
		let payloadData = txData.transactionEnvelope.payload.data;
		// logger.info(util.inspect(payloadData, false, null));
		// logger.info(new String(txData.transactionEnvelope.payload.data.actions[0].payload.chaincode_proposal_payload.array()));
		var proposal = protos.Proposal.decode(payloadData.actions[0].payload.chaincode_proposal_payload);
		// logger.info('================proposal==============');
		// logger.info(util.inspect(proposal, false, null));
		// Note: meat is in the header!!!
		var ccpp = protos.ChaincodeProposalPayload.decode(proposal.header);
		// logger.info('================chaincode proposal payload==============');
		var ccspec = protos.ChaincodeSpec.decode(ccpp.input);
		// logger.info(util.inspect(ccspec, false, null));
		// var inputs = protos.ChaincodeInput.decode(ccspec.input);
		var args = []
		for(let i = 0; i < ccspec.input.args.length ; ++i) {
            let bb = ccspec.input.args[i];
            // !!! not easy to figure out
		    args.push(bb.buffer.toString("utf8", bb.offset, bb.limit));
		}
		// logger.info(args);
		return args;
	})
	.catch(err => logger.error('error:', err));